技巧12 类宿主机容器
现在,我们将继续讨论一个在Docker社区里颇具争议的领域——运行一个从一开始就运行着多个进程的类宿主机镜像。
在Docker社区里,一部分人认为这是一个糟糕的做法。容器并不是虚拟机(有着显著差异),而且完全没办法假装说对此没有困惑或问题。
好也罢,坏也罢,本技巧将会展示该如何运行一个类宿主机镜像,然后讨论这种做法带来的一些问题。
注意
运行类宿主机镜像会是一个说服Docker反对派的好方法,告诉他们Docker是有用的。他们用得越多,对范式的理解就会越透彻,微服务方案对他们来说也就越有意义。在我们将Docker引入企业内部后,我们发现这种单体方式是一个很棒的切入点,它可以推动人们从以前的在开发服务器及笔记本上开发转换到一个更包容和可管理的环境。由此,将Docker推广到测试、持续集成、托管环境以及DevOps工作流也就水到渠成了。
虚拟机和Docker容器之间的差异
虚拟机和Docker容器之间存在以下不同之处。
- Docker是面向应用的,而虚拟机是面向操作系统的。
- Docker容器和其他容器共享同一个操作系统。相反,每台虚拟机都有一个由hypervisor管理的它们自己的操作系统。
- Docker容器被设计成只运行一个主要进程,而不是管理多组进程。
问题
想要自己的容器运行在一个正常的类宿主机环境中,可以运行多个进程和服务。
解决方案
使用一个被设计成可以运行多个进程的基础容器。
针对本技巧,将会使用一个被设计成用来模拟一台宿主机的镜像,然后用它置备需要部署的应用程序。这里我们打算使用 phusion/baseimage
Docker镜像,一个被设计成可以运行多个进程的镜像。
第一步就是启动该镜像然后使用 docker exec
连到它里面,如代码清单3-5所示。
代码清单3-5 运行phusion基础镜像
user@docker-host$ docker run -d phusion/baseimage ⇽--- 在后台启动该镜像
3c3f8e3fb05d795edf9d791969b21f7f73e99eb1926a6e3d5ed9e1e52d0b446e ⇽--- 返回新容器的ID
user@docker-host$ docker exec -i -t 3c3f8e3fb05d795 /bin/bash ⇽--- 传递容器ID给docker exec,并分配交互式终端
root@3c3f8e3fb05d:/# ⇽--- 提示到已启动的容器终端
在上述代码中, docker run
将会在后台启动该镜像,执行该镜像默认的启动命令,然后返回新创建的容器的ID。
随后可以将这个容器的ID传给 docker exec
,该命令会在这个已经运行的容器内部启动一个新的进程。 -i
标志代表着可以和新进程交互,而 -t
则意味着想要配置一个TTY,它允许在容器内部开启一个终端( /bin/bash
)。
如果等待1分钟,然后查看进程表,输出内容将会如代码清单3-6所示。
代码清单3-6 一个类宿主机容器里正在运行的进程
root@3c3f8e3fb05d:/# ps -ef ⇽--- 执行ps命令列出所有正在运行的进程
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 13:33 ? 00:00:00 /usr/bin/python3 -u /sbin/my_init ⇽--- 一个简单的init进程,设计用来运行所有其他服务
root 7 0 0 13:33 ? 00:00:00 /bin/bash ⇽--- bash进程由docker exec启动,并且当作shell使用
root 111 1 0 13:33 ? 00:00:00 /usr/bin/runsvdir -P /etc/service ⇽--- runsvdir运行所有在/etc/service目录里定义的服务
root 112 111 0 13:33 ? 00:00:00 runsv cron
root 113 111 0 13:33 ? 00:00:00 runsv sshd ⇽--- 通过runsv命令在这里启动3个标准服务(cron、sshd和syslog)
root 114 111 0 13:33 ? 00:00:00 runsv syslog-ng
root 115 112 0 13:33 ? 00:00:00 /usr/sbin/cron -f
root 116 114 0 13:33 ? 00:00:00 syslog-ng -F -p /var/run/syslog-ng.pid
➥ --no-caps
root 117 113 0 13:33 ? 00:00:00 /usr/sbin/sshd -D
root 125 7 0 13:38 ? 00:00:00 ps -ef ⇽--- 当前执行的ps命令
可以看到,容器的启动过程很像一台宿主机,初始化一些像cron和sshd这样的服务,这使它看上去和一台标准的Linux主机没什么两样。
讨论
尽管本技巧在给刚接触Docker的工程师做入门演示时很有帮助,或者针对某些特定场景特别管用,但是值得注意的是,这是一个有争议的想法。
一直以来,容器的使用方式倾向于利用它们将工作负载隔离为“每个容器一个服务”。类宿主机镜像方案的支持者认为这样做并没有违背该原则,因为容器仍然可以满足为里面运行的系统提供单一的离散功能的需求。
近段时间Kubernetes的pod和docker-compose概念的日益普及使得类宿主机的容器显得相对冗余——宏观上来说单个容器可以对应到单个实体,而不是使用传统的init服务管理多个进程。
技巧13将着眼于如何把这种单体应用程序分解为微服务式的容器。